#!/usr/bin/python
# -*- coding: utf-8 -*-
# @Version        : 1.2
# @Update Time    : 2025/8/26 11:00
# @File           : qiniu_file.py
# @IDE            : PyCharm
# @Desc           : 七牛云文件上传策略，支持普通文件、图片、音频和视频的上传，增强版本

import os
import time
from typing import Optional, List, Dict
from fastapi import UploadFile
from qiniu import Auth, BucketManager, put_data
from applications.config import settings
from applications.utils.upload_file.base import AbstractUpload, UploadResult
from applications.utils.upload_file.file_utils import FileUtils
from applications.core.exceptions import BaseAppException


class QiniuFileStrategy(AbstractUpload):
    """七牛云文件上传策略

    使用七牛云作为文件存储服务，支持图片、视频、音频等文件的上传、删除和查询。
    支持分片上传功能，适用于大文件传输。

    Attributes:
        access_key: 七牛云 AccessKey
        secret_key: 七牛云 SecretKey
        bucket_name: 存储空间名称
        domain: 七牛云域名
        auth: 七牛云认证对象
        bucket_manager: 七牛云 BucketManager 对象
    """

    def __init__(self):
        """初始化七牛云上传策略"""
        self.access_key = getattr(settings, "QINIU_ACCESS_KEY", "")
        self.secret_key = getattr(settings, "QINIU_SECRET_KEY", "")
        self.bucket_name = getattr(settings, "QINIU_BUCKET_NAME", "")
        self.domain = getattr(settings, "QINIU_DOMAIN", "")

        if not all([self.access_key, self.secret_key, self.bucket_name, self.domain]):
            raise BaseAppException(msg="七牛云配置信息不完整")

        self.auth = Auth(self.access_key, self.secret_key)
        self.bucket_manager = BucketManager(self.auth)
        # 分片大小 (4MB，七牛云要求分片大小在1MB-15MB之间)
        self.CHUNK_SIZE = 4 * 1024 * 1024
        # 上传路径
        self.UPLOAD_PATH = getattr(settings, "STATIC_PATH", "static")

    @classmethod
    def get_storage_strategy(cls) -> str:
        """获取存储策略类型"""
        return "qiniu"

    async def upload_with_result(
        self,
        path: str,
        file: UploadFile,
        accept: Optional[List[str]] = None,
        max_size: int = 5,
        create_thumbnail: bool = False,
    ) -> UploadResult:
        """上传文件并返回详细结果

        Args:
            path: 保存路径
            file: 上传的文件
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)
            create_thumbnail: 是否创建缩略图（对七牛云暂不支持）

        Returns:
            UploadResult: 上传结果对象
        """
        if not accept:
            accept = self.ALL_ACCEPT

        return await self._upload_file_enhanced(path, file, accept, max_size)

    async def batch_upload(
        self, path: str, files: List[UploadFile], accept: Optional[List[str]] = None, max_size: int = 5
    ) -> List[UploadResult]:
        """批量上传文件

        Args:
            path: 保存路径
            files: 上传的文件列表
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)

        Returns:
            List[UploadResult]: 上传结果列表
        """
        results = []
        for file in files:
            try:
                result = await self.upload_with_result(path, file, accept, max_size)
                results.append(result)
            except Exception as e:
                error_result = UploadResult(success=False, error=str(e), message=f"文件 {file.filename} 上传失败")
                results.append(error_result)
        return results

    async def get_file_url(self, file_path: str) -> str:
        """获取文件访问URL

        Args:
            file_path: 文件路径

        Returns:
            str: 文件访问URL
        """
        # 如果已经是完整URL，直接返回
        if file_path.startswith("http"):
            return file_path
        # 如果包含域名，直接返回
        if self.domain in file_path:
            return file_path
        # 构建完整URL
        return f"{self.domain.rstrip('/')}/{file_path.lstrip('/')}"

    async def file_exists(self, file_path: str) -> bool:
        """检查文件是否存在

        Args:
            file_path: 文件路径

        Returns:
            bool: 文件是否存在
        """
        try:
            # 从完整URL中提取文件路径
            if file_path.startswith(self.domain):
                file_path = file_path[len(self.domain) + 1 :]

            ret, info = self.bucket_manager.stat(self.bucket_name, file_path)
            return ret is not None and info.status_code == 200
        except Exception:
            return False

    async def upload_file(
        self, path: str, file: UploadFile, accept: Optional[List[str]] = None, max_size: int = 5
    ) -> str:
        """上传普通文件到七牛云

        Args:
            path: 保存路径
            file: 上传的文件
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)

        Returns:
            str: 文件访问URL

        Raises:
            BaseAppException: 上传失败时抛出
        """
        if not accept:
            accept = self.FILE_ACCEPT
        result = await self._upload_file_enhanced(path, file, accept, max_size)
        return result.url

    async def upload_image(
        self, path: str, file: UploadFile, accept: Optional[List[str]] = None, max_size: int = 5
    ) -> str:
        """上传图片文件到七牛云

        Args:
            path: 保存路径
            file: 上传的文件
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)

        Returns:
            str: 文件访问URL

        Raises:
            BaseAppException: 上传失败时抛出
        """
        if not accept:
            accept = self.IMAGE_ACCEPT
        result = await self._upload_file_enhanced(path, file, accept, max_size)
        return result.url

    async def upload_audio(
        self, path: str, file: UploadFile, accept: Optional[List[str]] = None, max_size: int = 5
    ) -> str:
        """上传音频文件到七牛云

        Args:
            path: 保存路径
            file: 上传的文件
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)

        Returns:
            str: 文件访问URL

        Raises:
            BaseAppException: 上传失败时抛出
        """
        if not accept:
            accept = self.AUDIO_ACCEPT
        result = await self._upload_file_enhanced(path, file, accept, max_size)
        return result.url

    async def upload_video(
        self, path: str, file: UploadFile, accept: Optional[List[str]] = None, max_size: int = 5
    ) -> str:
        """上传视频文件到七牛云

        Args:
            path: 保存路径
            file: 上传的文件
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)

        Returns:
            str: 文件访问URL

        Raises:
            BaseAppException: 上传失败时抛出
        """
        if not accept:
            accept = self.VIDEO_ACCEPT
        result = await self._upload_file_enhanced(path, file, accept, max_size)
        return result.url

    async def delete_file(self, file_path: str) -> bool:
        """删除七牛云上的文件

        Args:
            file_path: 文件路径（七牛云上的相对路径）

        Returns:
            bool: 删除结果

        Raises:
            BaseAppException: 删除失败时抛出
        """
        try:
            # 从完整URL中提取文件路径
            if file_path.startswith(self.domain):
                file_path = file_path[len(self.domain) + 1 :]

            # 删除文件
            ret, info = self.bucket_manager.delete(self.bucket_name, file_path)

            if info.status_code == 200:
                return True
            else:
                raise BaseAppException(msg=f"删除失败: {info.error}")
        except Exception as e:
            raise BaseAppException(msg=f"删除文件失败: {str(e)}")

    async def get_file_info(self, file_path: str) -> dict:
        """获取七牛云上的文件信息

        Args:
            file_path: 文件路径（七牛云上的相对路径）

        Returns:
            dict: 文件信息

        Raises:
            BaseAppException: 获取失败时抛出
        """
        try:
            # 从完整URL中提取文件路径
            if file_path.startswith(self.domain):
                file_path = file_path[len(self.domain) + 1 :]

            # 获取文件信息
            ret, info = self.bucket_manager.stat(self.bucket_name, file_path)

            if ret and "fsize" in ret:
                return {
                    "file_path": file_path,
                    "url": await self.get_file_url(file_path),
                    "size": ret["fsize"],
                    "size_human": FileUtils.format_file_size(ret["fsize"]),
                    "modified_time": ret["putTime"],
                    "mimeType": ret.get("mimeType", ""),
                    "file_type": FileUtils.get_file_type_by_extension(file_path),
                }
            else:
                raise BaseAppException(msg=f"获取文件信息失败: {info.error}")
        except Exception as e:
            raise BaseAppException(msg=f"获取文件信息失败: {str(e)}")

    async def upload_large_file(
        self, path: str, file: UploadFile, accept: Optional[List[str]] = None, max_size: int = 50
    ) -> str:
        """上传大文件到七牛云

        Args:
            path: 保存路径
            file: 上传的文件
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)

        Returns:
            str: 文件访问URL

        Raises:
            BaseAppException: 上传失败时抛出
        """
        # 验证文件类型
        if not accept:
            accept = self.ALL_ACCEPT

        if file.content_type not in accept:
            raise BaseAppException(msg=f"不支持的文件类型: {file.content_type}")

        # 生成文件路径
        file_path = self.generate_relative_path(path, file.filename)

        try:
            # 读取文件内容
            content = await file.read()

            # 检查文件大小
            if len(content) > max_size * 1024 * 1024:
                raise BaseAppException(msg=f"文件大小超过限制: {max_size}MB")

            # 生成上传凭证
            token = self.auth.upload_token(self.bucket_name, file_path)

            # 上传文件
            ret, info = put_data(token, file_path, content)

            if ret and ret.get("key") == file_path:
                return f"{self.domain}/{file_path}"
            else:
                raise BaseAppException(msg=f"上传失败: {info.error}")
        except Exception as e:
            raise BaseAppException(msg=f"文件上传失败: {str(e)}")
        finally:
            await file.close()

    async def _upload_file_enhanced(
        self, path: str, file: UploadFile, accept: List[str], max_size: int
    ) -> UploadResult:
        """增强版上传文件到七牛云的内部方法

        Args:
            path: 保存路径
            file: 上传的文件
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)

        Returns:
            UploadResult: 上传结果

        Raises:
            BaseAppException: 上传失败时抛出
        """
        try:
            # 验证文件并获取详细信息
            file_info = await self.validate_file_advanced(file, max_size, accept)

            # 生成文件路径
            file_path = self.generate_relative_path(path, file.filename)

            # 读取文件内容
            content = await file.read()

            # 检查文件大小
            if len(content) > max_size * 1024 * 1024:
                raise BaseAppException(msg=f"文件大小超过限制: {max_size}MB")

            # 生成上传凭证
            token = self.auth.upload_token(self.bucket_name, file_path)

            # 上传文件
            ret, info = put_data(token, file_path, content)

            if ret and ret.get("key") == file_path:
                file_url = await self.get_file_url(file_path)

                # 构建详细的文件信息
                detailed_info = {
                    "file_path": file_path,
                    "filename": file.filename,
                    "size": len(content),
                    "size_human": FileUtils.format_file_size(len(content)),
                    "content_type": file.content_type,
                    "file_type": FileUtils.get_file_type_by_extension(file.filename or ""),
                    "storage_type": "qiniu",
                }

                return UploadResult(
                    success=True, file_path=file_path, url=file_url, file_info=detailed_info, message="文件上传成功"
                )
            else:
                raise BaseAppException(msg=f"上传失败: {info.error if info else '未知错误'}")
        except BaseAppException:
            raise
        except Exception as e:
            raise BaseAppException(msg=f"上传失败: {str(e)}")
        finally:
            await file.close()

    async def _upload_file(self, path: str, file: UploadFile, accept: List[str], max_size: int) -> str:
        """上传文件到七牛云的内部方法（保持向后兼容）

        Args:
            path: 保存路径
            file: 上传的文件
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)

        Returns:
            str: 文件访问URL

        Raises:
            BaseAppException: 上传失败时抛出
        """
        result = await self._upload_file_enhanced(path, file, accept, max_size)
        return result.url
